package org.example.service;

import io.micrometer.common.util.StringUtils;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.function.IOIterator;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.example.comfig.Config;
import org.example.domain.FileEntity;
import org.example.domain.FileInfo;
import org.example.domain.PreviewZipEntity;
import org.example.exception.ErrorCode;
import org.example.exception.ServiceException;
import org.example.util.Utils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.yaml.snakeyaml.util.UriEncoder;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {

    public static final Map<String,List<PreviewZipEntity>> cache = new ConcurrentHashMap<>();
    public static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());

    private final Config config;

    /**
     * 获取文件列表
     *
     * @param pathName 目录
     */
    public List<FileEntity> getDirectoryFile(String pathName) {
        if (StringUtils.isEmpty(pathName)) {
            return config.getVisibleDirectory().isEmpty() ? Arrays.stream(File.listRoots()).map(FileEntity::new).collect(Collectors.toList()) : config.getVisibleDirectory().stream().map(FileEntity::new).collect(Collectors.toList());
        }
        String replace = pathName.replace("/", "\\").replace("\\\\", "\\").toLowerCase();
        if(!config.getVisibleDirectory().isEmpty() && config.getVisibleDirectory().stream().noneMatch(dir -> replace.startsWith(dir.replace("/", "\\").replace("\\\\", "\\").toLowerCase()))) {
            throw new ServiceException("没有权限访问 " + pathName, ErrorCode.NOT_VISIBLE_DIRECTORY);
        }
        File file = new File(pathName);
        if(!file.isDirectory()) throw new RuntimeException("不是目录！");
        File[] files = file.listFiles();
        return Arrays.stream(Optional.ofNullable(files).orElse(new File[0])).map(FileEntity::new).collect(Collectors.toList());
    }

    /**
     * 搜索文件
     */
    public List<FileEntity> search(String path, String name) throws ExecutionException, InterruptedException {
        if(StringUtils.isEmpty(path)) {
            File[] files = File.listRoots();
            if(!config.getVisibleDirectory().isEmpty()) {
                files = config.getVisibleDirectory().stream().map(File::new).toArray(File[]::new);
            }
            ArrayList<CompletableFuture<List<FileEntity>>> list = new ArrayList<>();
            for (File file : files) {
                list.add(CompletableFuture.supplyAsync(() -> traversal(file, name)));
            }

            CompletableFuture.allOf(list.toArray(new CompletableFuture[0])).join();

            ArrayList<FileEntity> fileList = new ArrayList<>();
            for (CompletableFuture<List<FileEntity>> listCompletableFuture : list) {
                fileList.addAll(listCompletableFuture.get());
            }
            return fileList;
        }
        String replace = path.replace("/", "\\").replace("\\\\", "\\").toLowerCase();
        if (!config.getVisibleDirectory().isEmpty() && config.getVisibleDirectory().stream().noneMatch(dir -> replace.startsWith(dir.replace("/", "\\").replace("\\\\", "\\").toLowerCase()))) {
            throw new ServiceException("没有权限访问 " + path, ErrorCode.NOT_VISIBLE_DIRECTORY);
        }
        File file = new File(path);
        return traversal(file, name);
    }

    public List<FileEntity> traversal(File file, String name)  {
        List<FileEntity> list = new ArrayList<>();

        if(file.getName().contains(name)) list.add(new FileEntity(file));
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            if(files == null) return new ArrayList<>();
            for (File listFile : files) {
                List<FileEntity> traversal = traversal(listFile, name);
                list.addAll(traversal);
            }
        }

        return list;
    }


    public void download(String absolutePath, HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
        File file = new File(absolutePath);
        if(file.isDirectory()) {
            downloadZip(new String[]{absolutePath}, response, false);
        }
        try (BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(file.toPath()))){
            response.setHeader("Content-Disposition", "attachment;filename=" + UriEncoder.encode(file.getName()));
            response.setContentLengthLong(file.length());
            BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
            IOUtils.copy(inputStream, outputStream);
            outputStream.close();
        }
    }

    public void download(String absolutePath, HttpServletResponse response, HttpServletRequest request) throws IOException, ExecutionException, InterruptedException {
        File file = new File(absolutePath);
        if (file.isDirectory()) {
            downloadZip(new String[]{file.getAbsolutePath()}, response,false);
            return;
        }

        // 获取请求头中的Range信息
        String rangeHeader = request.getHeader("Range");

        // 不是多线程下载
        if(rangeHeader == null) {
            download(absolutePath, response);
            return;
        }

        long fileSize = file.length();
        long start = 0;
        long end = fileSize - 1;

        // 解析Range头（示例：bytes=0-999）
        if (rangeHeader.startsWith("bytes=")) {
            String[] range = rangeHeader.substring(6).split("-");
            start = Long.parseLong(range[0]);
            if (range.length > 1) {
                end = Long.parseLong(range[1]);
            }
            end = Math.min(end, fileSize - 1);
        }

        // 设置响应头
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206
        response.setHeader("Content-Type", "application/octet-stream");
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8));
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
        response.setHeader("Content-Length", String.valueOf(end - start + 1));

        // 使用FileChannel进行高效范围读取
        try (RandomAccessFile raf = new RandomAccessFile(file, "r");
             FileChannel channel = raf.getChannel();
             OutputStream os = response.getOutputStream()) {

            channel.position(start);
            long remaining = end - start + 1;
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB直接缓冲区

            while (remaining > 0) {
                int read = channel.read(buffer);
                if (read < 0) break;

                buffer.flip();
                byte[] arr = new byte[read];
                buffer.get(arr);
                os.write(arr);

                buffer.clear();
                remaining -= read;
            }
        }
    }

    public void icon(String absolutePath, HttpServletResponse response) throws IOException {
        // 获取文件的系统图标
        Icon icon = getMaxIcon(absolutePath);

        // 创建一个缓冲图像来绘制图标
        int width = icon.getIconWidth();
        int height = icon.getIconHeight();
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics graphics = bufferedImage.createGraphics();
        icon.paintIcon(null, graphics, 0, 0);
        graphics.dispose();

        // 设置响应头
        response.setContentType("image/png");
        response.setHeader("Content-Disposition", "inline; filename=icon.png");

        // 将图像写入输出流
        try (OutputStream outputStream = response.getOutputStream()) {
            ImageIO.write(bufferedImage, "png", outputStream);
        }
    }

    private Icon getMaxIcon(String absolutePath) {
        int[] sizes = {64, 48, 32, 16};
        FileSystemView fileSystemView = FileSystemView.getFileSystemView();
        File file = new File(absolutePath);
        for (int size : sizes) {
            try {
                return fileSystemView.getSystemIcon(file,size,size);
            } catch (Exception e) {
                log.warn("获取图标失败：{}",e.getMessage());
            }
        }
        return fileSystemView.getSystemIcon(file);
    }

    public void downloadZip(String[] absolutePathList, HttpServletResponse response, boolean compressed) throws IOException, ExecutionException, InterruptedException {
        ZipOutputStream outputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
        for (String s : absolutePathList) {
            File file = new File(s);
            zip(file, outputStream, "", compressed);
        }
        outputStream.close();
    }

    private static void zip(File file, ZipOutputStream outputStream, String parent, boolean compressed) throws IOException {
        if(file.isFile()) {
            InputStream inputStream = new BufferedInputStream(new FileInputStream(file));

            CRC32 crc32 = new CRC32();
            if (!compressed) {
                byte[] buffer = new byte[8192];
                int bytesRead;

//             计算文件的 CRC32 校验和
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    crc32.update(buffer, 0, bytesRead);
                }
                inputStream = new BufferedInputStream(new FileInputStream(file));
            }


            ZipEntry zipEntry = new ZipEntry(parent + File.separator +file.getName());
            if(!compressed){
                zipEntry.setMethod(ZipEntry.STORED);
                zipEntry.setSize(file.length());
                zipEntry.setCompressedSize(file.length());
                zipEntry.setCrc(crc32.getValue());
            }

            outputStream.putNextEntry(zipEntry);
            IOUtils.copy(inputStream, outputStream);
            inputStream.close();
        }
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            if(files == null) return;
            for (File file1 : files) {
                String parent1 = file1.getParent();
                zip(file1,outputStream, parent + File.separator + parent1.substring(parent1.lastIndexOf("\\")+1),compressed);
            }
        }
    }

    public List<PreviewZipEntity> previewZip(String absolutePath, String directoryName) throws IOException {
        List<PreviewZipEntity> previewZipEntities = cache.get(absolutePath);
        if(previewZipEntities == null) {
            File file = new File(absolutePath);
            previewZipEntities = getPreviewZipEntities(file);
            cache.put(absolutePath,previewZipEntities);
            executorService.schedule(() -> {
                cache.remove(absolutePath);
            },1,TimeUnit.MINUTES);
        }

        String[] split = directoryName.split("/");
        int length = split.length;
        if (length != 1 || !split[0].isEmpty()) length++;
        int finalLength = length;
        return previewZipEntities.stream().filter(item -> finalLength == item.getLevel() && item.getAbsolutePath().startsWith(directoryName)).collect(Collectors.toList());
    }

    private static List<PreviewZipEntity> getPreviewZipEntities(File file) throws IOException {
        ZipArchiveInputStream inputStream = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(file)));
        List<PreviewZipEntity> list = new ArrayList<>();
        IOIterator<ZipArchiveEntry> iterator = inputStream.iterator();
        while (iterator.hasNext()) {
            ZipArchiveEntry zipArchiveEntry = iterator.next();
            PreviewZipEntity previewZipEntity = new PreviewZipEntity();
            previewZipEntity.setDirectory(zipArchiveEntry.isDirectory());
            previewZipEntity.setFile(!zipArchiveEntry.isDirectory());
            previewZipEntity.setAbsolutePath(zipArchiveEntry.getName());
            previewZipEntity.setName(Arrays.stream(zipArchiveEntry.getName().replace("/", "\\").split("\\\\")).filter(item -> !item.isEmpty()).toList().getLast());
            previewZipEntity.setSize(zipArchiveEntry.getSize());
            previewZipEntity.setSizeStr(zipArchiveEntry.isDirectory() ? "--" : Utils.byteConversion(zipArchiveEntry.getSize()));
            previewZipEntity.setLevel(zipArchiveEntry.getName().split("/").length);
            list.add(previewZipEntity);
        }
        return list;
    }

    public void upload(MultipartFile file, String absolutePath) throws IOException {
        boolean contains = file.getOriginalFilename().contains(".");
        String fileName = contains ? file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf(".")) : file.getOriginalFilename();
        String suffix = contains ? file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")) : "";
        File newFile = new File(absolutePath + File.separator + fileName + suffix);
        int count = 1;
        while (newFile.exists()) {
            newFile = new File(absolutePath + File.separator + fileName + "(" + count++ + ")" + suffix);
        }
        file.transferTo(newFile);
    }

    public void openZipFile(String absolutePath, String fileName, HttpServletResponse response, HttpServletRequest request) throws IOException {
        ZipFile zipFile = ZipFile.builder().setFile(absolutePath).get();
        ZipArchiveEntry entry = zipFile.getEntry(fileName);
        // 获取请求头中的Range信息
        String rangeHeader = request.getHeader("Range");
        if(rangeHeader == null) {
            openZipFileDownload(absolutePath, fileName, response);
            return;
        }
        long fileSize = entry.getSize();
        long start = 0;
        long end = fileSize - 1;

        // 解析Range头（示例：bytes=0-999）
        if (rangeHeader.startsWith("bytes=")) {
            String[] range = rangeHeader.substring(6).split("-");
            start = Long.parseLong(range[0]);
            if (range.length > 1) {
                end = Long.parseLong(range[1]);
            }
            end = Math.min(end, fileSize - 1);
        }
        String[] split = fileName.split("/");
        // 设置响应头
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206
        response.setHeader("Content-Type", "application/octet-stream");
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(split[split.length > 1 ? split.length - 1 : 0], StandardCharsets.UTF_8));
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
        response.setHeader("Content-Length", String.valueOf(end - start + 1));

        InputStream inputStream = zipFile.getInputStream(entry);
        ServletOutputStream outputStream = response.getOutputStream();
        long skip = inputStream.skip(start);
        long remaining = end - start + 1;
        byte[] bytes = new byte[1024 * 1024]; // 1MB直接缓冲区

        while (remaining > 0) {
            int read = inputStream.read(bytes);
            if (read < 0) break;
            outputStream.write(bytes, 0, read);
            remaining -= read;
        }
    }
    public void openZipFileDownload(String absolutePath, String fileName, HttpServletResponse response) throws IOException {
        ZipFile zipFile = ZipFile.builder().setFile(absolutePath).get();
        ZipArchiveEntry entry = zipFile.getEntry(fileName);
        response.setHeader("Content-Type", "application/octet-stream");
        String[] split = fileName.split("/");
        if (entry.isDirectory()) {
            response.setHeader("Content-Disposition",
                    "attachment;filename=" + URLEncoder.encode(split[split.length > 1 ? split.length - 1 : 0] + ".zip", StandardCharsets.UTF_8));
            ZipOutputStream outputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry zipEntry = entries.nextElement();
                if(zipEntry.isDirectory()) continue;
                if(zipEntry.getName().startsWith(fileName)) {
                    ZipEntry zipEntry1 = new ZipEntry(zipEntry.getName());
                    CRC32 crc32 = new CRC32();
                    InputStream inputStream = zipFile.getInputStream(zipEntry);
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        crc32.update(buffer, 0, bytesRead);
                    }
                    zipEntry1.setMethod(ZipEntry.STORED);
                    zipEntry1.setCrc(crc32.getValue());
                    zipEntry1.setSize(zipEntry.getSize());
                    outputStream.putNextEntry(zipEntry1);
                    IOUtils.copy(zipFile.getInputStream(zipEntry), outputStream);
                    outputStream.closeEntry();
                }
            }
            outputStream.close();
            return;
        }

        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(split[split.length > 1 ? split.length - 1 : 0], StandardCharsets.UTF_8));
        response.setContentLengthLong(entry.getSize());
        IOUtils.copy(zipFile.getInputStream(entry), response.getOutputStream());
    }

    public FileInfo info(String[] absolutePathList) {
        List<File> files = new ArrayList<>();
        for (String s : absolutePathList) {
            files.addAll(getAllFiles(new File(s)));
        }
        FileInfo fileInfo = new FileInfo();
        long count = files.stream().filter(File::isFile).count();
        fileInfo.setFileNum(count);
        fileInfo.setDirNum(files.size() - count);
        fileInfo.setName(files.size() > 1 ? fileInfo.getDirNum() + "个文件夹，" + fileInfo.getFileNum() + "个文件" : files.getFirst().getName());
        fileInfo.setSize(files.stream().mapToLong(File::length).sum());
        fileInfo.setSizeStr(Utils.byteConversion(fileInfo.getSize()));
        return fileInfo;
    }

    private List<File> getAllFiles(File file) {
        List<File> list = new ArrayList<>();
        list.add(file);
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            if(files == null) return list;
            for (File file1 : files) {
                list.addAll(getAllFiles(file1));
            }
        }
        return list;
    }

    public String text(String absolutePath) throws IOException {
        // 获取文件的内容
        File file = new File(absolutePath);
        // 大于2M的文件不返回
        if(file.length() > 2 * 1024 * 1024) {
            throw new RuntimeException("文件过大,无法展示文本,请下载后查看");
        }
        FileInputStream fileInputStream = new FileInputStream(absolutePath);
        byte[] bytes = fileInputStream.readAllBytes();
        fileInputStream.close();
        return new String(bytes, StandardCharsets.UTF_8);
    }
}
